# -*- coding: utf-8 -*-

import numpy as np
import cv2
from common.config import config_option
import matplotlib.pyplot as plt

"""
目标
　　• 你将学习如何对图像进行颜色空间转换，比如从 BGR 到灰度图，或者从BGR 到 HSV 等。
　　• 我没还要创建一个程序用来从一幅图像中获取某个特定颜色的物体。
　　• 我们将要学习的函数有：cv2.cvtColor()，cv2.inRange() 等。
"""


def show_image(img):
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def color_space_conversion():
    """
    转换颜色空间：
        在 OpenCV 中有超过 150 中进行颜色空间转换的方法。但是你以后就会、发现我们经常用到的也就两种：BGR<-->Gray 和 BGR<-->HSV。
        我们要用到的函数是：cv2.cvtColor(input_image ，flag)，其中 flag就是转换类型。
        对于 BGR<-->Gray 的转换，我们要使用的 flag 就是 cv2.COLOR_BGR2GRAY。同样对于 BGR<-->HSV 的转换，我们用的 flag 就是 cv2.COLOR_BGR2HSV。
        你还可以通过下面的命令得到所有可用的 flag。

        注意：在 OpenCV 的 HSV 格式中，H（色彩/色度）的取值范围是 [0，179]，S（饱和度）的取值范围 [0，255]，V（亮度）的取值范围 [0，255]。
        但是不同的软件使用的值可能不同。所以当你需要拿 OpenCV 的 HSV 值与其他软件的 HSV 值进行对比时，一定要记得归一化。
    """
    flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
    print(flags)


def object_track():
    """
    物体跟踪
        现在我们知道怎样将一幅图像从 BGR 转换到 HSV 了，我们可以利用这一点来提取带有某个特定颜色的物体。
        在 HSV 颜色空间中要比在 BGR 空间中更容易表示一个特定颜色。
        在我们的程序中，我们要提取的是一个蓝色的物体。下面就是就是我们要做的几步：
            • 从视频中获取每一帧图像
            • 将图像转换到 HSV 空间
            • 设置 HSV 阈值到蓝色范围。
            • 获取蓝色物体，当然我们还可以做其他任何我们想做的事，比如：在蓝色物体周围画一个圈。
    """
    cap = cv2.VideoCapture(config_option['project_path'] + '/data/video/a1.mp4')

    while 1:
        ret, frame = cap.read()

        # Convert BGR to HSV
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        # define range of blue color in HSV
        lower_blue = np.array([110, 50, 50])
        upper_blue = np.array([130, 255, 255])

        # Threshold the HSV image to get only blue colors
        mask = cv2.inRange(hsv, lower_blue, upper_blue)

        # Bitwise-AND mask and original image
        res = cv2.bitwise_and(frame, frame, mask=mask)

        cv2.imshow('frame', frame)
        cv2.imshow('mask', mask)
        cv2.imshow('res', res)
        k = cv2.waitKey(5) & 0xFF
        if k == 27:
            break

    cv2.destroyAllWindows()


"""
目标
　　• 学习对图像进行各种几个变换，例如移动，旋转，仿射变换等。
　　• 将要学到的函数有：cv2.getPerspectiveTransform。
"""


def geometric_transform():
    """
    几何变换
        OpenCV 提供了两个变换函数，cv2.warpAffine 和 cv2.warpPerspective，使用这两个函数你可以实现所有类型的变换。
        cv2.warpAffine 接收的参数是2 × 3 的变换矩阵，而 cv2.warpPerspective 接收的参数是 3 × 3 的变换矩阵。

        扩展缩放：
            扩展缩放只是改变图像的尺寸大小。OpenCV 提供的函数 cv2.resize()可以实现这个功能。图像的尺寸可以自己手动设置，你也可以指定缩放因子。
            我们可以选择使用不同的插值方法。在缩放时我们推荐使用 cv2.INTER_AREA，在扩展时我们推荐使用 v2.INTER_CUBIC（慢) 和 v2.INTER_LINEAR。
            默认情况下所有改变图像尺寸大小的操作使用的插值方法都是 cv2.INTER_LINEAR。你可以使用下面任意一种方法改变图像的尺寸：

        平移：
            平移就是将对象换一个位置。如果你要沿（x，y）方向移动，移动的距离是（t x ，t y ），构建移动矩阵：
            使用 Numpy 数组构建这个矩阵（数据类型是 np.float32），然后把它传给函数 cv2.warpAffine()。
            下面这个例子，它被移动了（100,50）个像素

        旋转：
            对一个图像旋转角度 θ
            getRotationMatrix2D： 第一个参数为旋转中心，第二个为旋转角度，第三个为旋转后的缩放因子
            warpAffine： 第三个参数是输出图像的尺寸中心

        仿射变换：
            在仿射变换中，原图中所有的平行线在结果图像中同样平行。为了创建这个矩阵我们需要从原图像中找到三个点以及他们在输出图像中的位置。
            然后cv2.getAffineTransform 会创建一个 2x3 的矩阵，最后这个矩阵会被传给函数 cv2.warpAffine。

        透视变换：
            对于视角变换，我们需要一个 3x3 变换矩阵。在变换前后直线还是直线。
            要构建这个变换矩阵，你需要在输入图像上找 4 个点，以及他们在输出图像上对应的位置。这四个点中的任意三个都不能共线。
            这个变换矩阵可以有函数 cv2.getPerspectiveTransform() 构建。然后把这个矩阵传给函数cv2.warpPerspective。

    """

    # # 扩展缩放
    # img = cv2.imread(project_path + '/data/graph/t1.png', cv2.IMREAD_COLOR)
    # res = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
    # height, width = img.shape[:2]
    # res = cv2.resize(img, (2 * width, 2 * height), interpolation=cv2.INTER_CUBIC)
    # show_image(res)
    #
    # # 平移
    # img = cv2.imread(project_path + '/data/graph/m1.jpg', cv2.IMREAD_COLOR)
    # rows, cols, channel = img.shape
    #
    # M = np.float32([[1, 0, 100], [0, 1, 50]])
    # dst = cv2.warpAffine(img, M, (cols, rows))
    # show_image(dst)
    #
    # # 旋转
    # img = cv2.imread(project_path + '/data/graph/m1.jpg', cv2.IMREAD_COLOR)
    # rows, cols, channel = img.shape
    # M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 0.6)
    # dst = cv2.warpAffine(img, M, (2 * cols, 2 * rows))
    # show_image(dst)

    # 透视变换
    img = cv2.imread(config_option['project_path'] + '/data/graph/m1.jpg', cv2.IMREAD_COLOR)
    rows, cols, ch = img.shape
    print(img.shape)
    pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
    pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
    M = cv2.getPerspectiveTransform(pts1, pts2)
    dst = cv2.warpPerspective(img, M, (1080, 1920))
    plt.subplot(121), plt.imshow(img), plt.title('Input')
    plt.subplot(122), plt.imshow(dst), plt.title('Output')
    plt.show()


"""
目标
　　• 本节你将学到简单阈值，自适应阈值，Otsu’s 二值化等
　　• 将要学习的函数有 cv2.threshold，cv2.adaptiveThreshold 等。
"""


def image_threshold():
    """
    图像阈值
        简单阈值：
            与名字一样，这种方法非常简单。但像素值高于阈值时，我们给这个像素赋予一个新值（可能是白色），否则我们给它赋予另外一种颜色（也许是黑色）。
            这个函数就是 cv2.threshhold()。这个函数的第一个参数就是原图像，原图像应该是灰度图。第二个参数就是用来对像素值进行分类的阈值。
            第三个参数就是当像素值高于（有时是小于）阈值时应该被赋予的新的像素值。OpenCV提供了多种不同的阈值方法，这是有第四个参数来决定的。这些方法包括：
            　　• cv2.THRESH_BINARY
            　　• cv2.THRESH_BINARY_INV
            　　• cv2.THRESH_TRUNC
            　　• cv2.THRESH_TOZERO
            　　• cv2.THRESH_TOZERO_INV

        自适应阈值：
            在前面的部分我们使用是全局阈值，整幅图像采用同一个数作为阈值。当时这种方法并不适应与所有情况，尤其是当同一幅图像上的不同部分的具有不同亮度时。
            这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值，
            从而使我们能在亮度不同的情况下得到更好的结果。

            这种方法需要我们指定三个参数，返回值只有一个。
                • Adaptive Method- 指定计算阈值的方法。
                    – cv2.ADPTIVE_THRESH_MEAN_C：阈值取自相邻区域的平均值
                    – cv2.ADPTIVE_THRESH_GAUSSIAN_C：阈值取值相邻区域的加权和，权重为一个高斯窗口。
                • Block Size - 邻域大小（用来计算阈值的区域大小）。
                • C - 这就是是一个常数，阈值就等于的平均值或者加权平均值减去这个常数。

        Otsu’ ’s 二值化：
            在第一部分中我们提到过 retVal，当我们使用 Otsu 二值化时会用到它。那么它到底是什么呢？

            在使用全局阈值时，我们就是随便给了一个数来做阈值，那我们怎么知道我们选取的这个数的好坏呢？答案就是不停的尝试。如果是一副双峰图像
            （简单来说双峰图像是指图像直方图中存在两个峰）呢？我们岂不是应该在两个峰之间的峰谷选一个值作为阈值？这就是 Otsu 二值化要做的。
            简单来说就是对一副双峰图像自动根据其直方图计算出一个阈值。（对于非双峰图像，这种方法得到的结果可能会不理想）。

            这里用到到的函数还是 cv2.threshold()，但是需要多传入一个参数（flag）：cv2.THRESH_OTSU。这时要把阈值设为 0。
            然后算法会找到最优阈值，这个最优阈值就是返回值 retVal。如果不使用 Otsu 二值化，返回的retVal 值与设定的阈值相等。

            下面的例子中，输入图像是一副带有噪声的图像。第一种方法，我们设127 为全局阈值。第二种方法，我们直接使用 Otsu 二值化。
            第三种方法，我们首先使用一个 5x5 的高斯核除去噪音，然后再使用 Otsu 二值化。看看噪音去除对结果的影响有多大吧。

    """

    # 简单阈值
    img = cv2.imread(config_option['project_path'] + '/data/graph/m1.jpg', cv2.IMREAD_COLOR)
    ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
    ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
    ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
    ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
    titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
    images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
    for i in range(6):
        plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()

    # 自适应阈值
    img = cv2.imread(config_option['project_path'] + '/data/graph/m1.jpg', cv2.IMREAD_GRAYSCALE)
    img = cv2.medianBlur(img, 5)
    ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
    th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
    titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding',
              'Adaptive Gaussian Thresholding']
    images = [img, th1, th2, th3]
    for i in range(4):
        plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()

    # Otsu’ ’s 二值化
    img = cv2.imread(config_option['project_path'] + '/data/graph/m1.jpg', cv2.IMREAD_GRAYSCALE)
    ret1, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    blur = cv2.GaussianBlur(img, (5, 5), 0)
    ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    # plot all the images and their histograms
    images = [img, 0, th1,
              img, 0, th2,
              blur, 0, th3]
    titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)',
              'Original Noisy Image', 'Histogram', "Otsu's Thresholding",
              'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]
    for i in range(3):
        plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray')
        plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])
        plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)
        plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])
        plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray')
        plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])
    plt.show()


"""
图像平滑

目标
　　• 学习使用不同的低通滤波器对图像进行模糊
　　• 使用自定义的滤波器对图像进行卷积（2D 卷积）
"""


def image_smooth():
    """
    图像平滑
    """






if __name__ == '__main__':
    # color_space_conversion()
    # object_track()
    # geometric_transform()
    image_threshold()
